package com.xuegao.xuegaospringboottest.utils;

import com.xuegao.xuegaospringboottest.config.RedisConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

public class RedisUtil {
    private static final Logger log = LoggerFactory.getLogger(RedisUtil.class);

    /**
     * 解锁Lua脚本
     */
    private static final String LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    /**
     * 默认3秒
     */
    private static final long DEFAULT_SECONDS = 3;

    private static RedisTemplate<String, Object> REDIS_TEMPLATE = SpringUtils.getBean(RedisConfig.REDIS_TEMPLATE_NAME, RedisTemplate.class);

    public static void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        if (REDIS_TEMPLATE == null) {
            REDIS_TEMPLATE = redisTemplate;
        }
    }

    private static RedisTemplate<String, Object> getRedisTemplate() {
        if (REDIS_TEMPLATE == null) {
            throw new RuntimeException("请自行设置redisTemplate或扫描使用RedisUtilConfig配置后，才可正常使用");
        }
        return REDIS_TEMPLATE;
    }


    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @return true=设置成功；false=设置失败
     */
    public static boolean expire(final String key, final long timeout) {

        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true=设置成功；false=设置失败
     */
    public static boolean expire(final String key, final long timeout, final TimeUnit unit) {

        Boolean ret = getRedisTemplate().expire(key, timeout, unit);
        return ret != null && ret;
    }

    /**
     * 删除单个key
     *
     * @param key 键
     * @return true=删除成功；false=删除失败
     */
    public static boolean del(final String key) {
        Boolean ret = getRedisTemplate().delete(key);
        return ret != null && ret;
    }

    /**
     * 删除多个key
     *
     * @param keys 键集合
     * @return 成功删除的个数
     */
    public static long del(final Collection<String> keys) {

        Long ret = getRedisTemplate().delete(keys);
        return ret == null ? 0 : ret;
    }

    /**
     * 存入普通对象
     *
     * @param key   Redis键
     * @param value 值
     */
    public static void set(final String key, final Object value) {
        getRedisTemplate().opsForValue().set(key, value, 1, TimeUnit.MINUTES);
    }

    /**
     * 存入普通对象
     *
     * @param key     键
     * @param value   值
     * @param timeout 有效期，单位秒
     */
    public static void set(final String key, final Object value, final long timeout) {
        getRedisTemplate().opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 获取普通对象
     *
     * @param key 键
     * @return 对象
     */
    public static Object get(final String key) {
        return getRedisTemplate().opsForValue().get(key);
    }

    /**
     * 加锁，无阻塞
     *
     * @param key        锁
     * @param value      请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean lock(String key, long expireTime, String value) {
        //在一定时间内获取锁，超时则返回错误
        //Set命令返回OK，则证明获取锁成功
        Boolean ret = getRedisTemplate().opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(ret);
    }

    /**
     * 加锁，无阻塞
     * for循环重试，时间到了没获取到就报错
     *
     * @param key        锁
     * @param value      请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean lockV2(String key, long expireTime, String value) {
        int tryCount = 3;
        while (tryCount > 0) {
            Boolean ret = getRedisTemplate().opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(ret)) {
                return true;
            }
            tryCount--;
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (Exception e) {
                log.error("线程被中断" + Thread.currentThread().getId(), e);
            }
        }
        return false;
    }

    /**
     * 使用lua脚本解锁，不会解除别人锁
     */
    public static boolean unLock(String key, String value) {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(LOCK_LUA_SCRIPT);
        redisScript.setResultType(Boolean.class);
        //没有指定序列化方式，默认使用上面配置的
        Object result = getRedisTemplate().execute(redisScript, Collections.singletonList(key), value);
        return Boolean.TRUE.equals(result);
    }
}
